package top.chukongxiang.monitorservice.tools;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.text.NamingCase;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.ds.DSFactory;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.level.Level;
import cn.hutool.setting.Setting;
import cn.hutool.setting.yaml.YamlUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import top.chukongxiang.monitorservice.MonitorServiceApplication;
import top.chukongxiang.monitorservice.mapper.SystemConfigMapper;
import top.chukongxiang.monitorservice.model.entity.SystemConfig;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.function.Consumer;

/**
 * @author 楚孔响
 * @version 1.0
 * @date 2023/10/24 14:36
 */
@Slf4j
public class H2Util {

    private static volatile DataSource dataSource;

    private static volatile SqlSessionFactory sqlSessionFactory;

    private static final String INIT_SQL_STR = ResourceUtil.readUtf8Str("init.h2.sql");

    public static void init() throws SQLException {

        if (dataSource != null) {
            return;
        }
        DbUtil.use(getDataSource())
                .tx(db -> {
                    List<String> sqls = StrUtil.split(INIT_SQL_STR, ';', true, true);
                    for (String sql : sqls) {
                        List<String> sqlLines = StrUtil.split(sql, '\n', true, true);
                        sqlLines.removeIf(sqlLine -> StrUtil.startWith(sqlLine,"--"));
                        String newSql = String.join("\n", sqlLines);
                        if (StrUtil.startWith(newSql, "SELECT ")) {
                            List<Entity> query = db.query(newSql);
                            log.info(JSONUtil.toJsonStr(query));
                        } else {
                            int line = db.execute(newSql);
                            log.info("影响行数：{}", line);
                        }
                    }
                });
    }

    public static DataSource getDataSource() {
        if (dataSource == null) {
            Setting setting = getSetting();

            DSFactory dsFactory = DSFactory.create(setting);
            dataSource = dsFactory.getDataSource();
        }
        return dataSource;
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        if (dataSource == null) {
            dataSource = getDataSource();
        }
        if (sqlSessionFactory == null) {
            MybatisSqlSessionFactoryBuilder builder = new MybatisSqlSessionFactoryBuilder();
            Environment environment = new Environment("h2", new JdbcTransactionFactory(), dataSource);
            MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(environment);

            MybatisPlusProperties mybatisPlusProperties = getMybatisPlusProperties();
            GlobalConfig srcGlobalConfig = mybatisPlusProperties.getGlobalConfig();
            GlobalConfig destGlobalConfig = GlobalConfigUtils.getGlobalConfig(mybatisConfiguration);
            BeanUtil.copyProperties(srcGlobalConfig, destGlobalConfig);

            MybatisPlusProperties.CoreConfiguration configuration = mybatisPlusProperties.getConfiguration();
            BeanUtil.copyProperties(configuration, mybatisConfiguration);

            MapperScan mapperScan = AnnotationUtil.getAnnotation(MonitorServiceApplication.class, MapperScan.class);
            String[] basePackages = mapperScan.value();
            for (String basePackage : basePackages) {
                mybatisConfiguration.getMapperRegistry().addMappers(basePackage);
            }

            sqlSessionFactory = builder.build(mybatisConfiguration);
        }
        return sqlSessionFactory;
    }

    public static void close() {
        if (dataSource != null) {
            DbUtil.close(dataSource);
        }
    }

    public static Setting getSetting() {
        ExpandDataSourceProperties dataSourceProperties = dataSourceProperties();
        Setting setting = new Setting();
        setting.put("url", dataSourceProperties.getUrl());
        setting.put("driver", dataSourceProperties.getDriverClassName());
        setting.put("username", dataSourceProperties.getUsername());
        setting.put("password", dataSourceProperties.getPassword());
        setting.put("showSql", dataSourceProperties.getShowSql().toString());
        setting.put("formatSql", dataSourceProperties.getFormatSql().toString());
        setting.put("showParams", dataSourceProperties.getShowParams().toString());
        setting.put("sqlLevel", dataSourceProperties.getSqlLevel().toString());
        return setting;
    }

    public static ExpandDataSourceProperties dataSourceProperties() {
        Dict dict = YamlUtil.load(ResourceUtil.getStream("application.yml"), Dict.class);
        ExpandDataSourceProperties dataSourceProperties = new ExpandDataSourceProperties();
        String driverClassName = dict.getByPath("spring.datasource.driver-class-name", String.class);
        dataSourceProperties.setDriverClassName(driverClassName);
        String url = dict.getByPath("spring.datasource.url", String.class);
        dataSourceProperties.setUrl(url);
        String username = dict.getByPath("spring.datasource.username", String.class);
        dataSourceProperties.setUsername(username);
        String password = dict.getByPath("spring.datasource.password", String.class);
        dataSourceProperties.setPassword(password);
        Boolean showSql = dict.getByPath("spring.datasource.show-sql", Boolean.class);
        dataSourceProperties.setShowSql(showSql);
        Boolean formatSql = dict.getByPath("spring.datasource.format-sql", Boolean.class);
        dataSourceProperties.setFormatSql(formatSql);
        Boolean showParams = dict.getByPath("spring.datasource.show-params", Boolean.class);
        dataSourceProperties.setShowParams(showParams);
        String sqlLevelStr = dict.getByPath("spring.datasource.sql-level", String.class);
        dataSourceProperties.setSqlLevel(Level.valueOf(sqlLevelStr.toUpperCase()));
        return dataSourceProperties;
    }

    public static MybatisPlusProperties getMybatisPlusProperties() {
        Dict dict = YamlUtil.load(ResourceUtil.getStream("application.yml"), Dict.class);
        Dict dict1 = new Dict(dict.getBean("mybatis-plus"));
        MybatisPlusProperties mybatisPlusProperties = new MybatisPlusProperties();
        convert(dict1).parseBean(mybatisPlusProperties);
        return mybatisPlusProperties;
    }

    private static Dict convert(Dict src) {
        if (src == null) {
            return null;
        }
        Dict dict = new Dict();
        for (String key : src.keySet()) {
            dict.set(NamingCase.toCamelCase(key, '-'), convert(Dict.parse(src.getBean(key))));
        }
        return dict;
    }

    @Data
    @EqualsAndHashCode(callSuper = true)
    public static class ExpandDataSourceProperties extends DataSourceProperties {
        private Boolean showSql;
        private Boolean formatSql;
        private Boolean showParams;
        private Level sqlLevel;
    }

    public static String getConfigValue(String key) {
        SystemConfig systemConfig = getConfig(key);
        return systemConfig == null ? null : systemConfig.getConfigValue();
    }
    public static SystemConfig getConfig(String key) {
        try (SqlSession sqlSession = getSqlSessionFactory().openSession(true)) {
            SystemConfigMapper systemConfigMapper = sqlSession.getMapper(SystemConfigMapper.class);
            return systemConfigMapper.selectOne(Wrappers.<SystemConfig>lambdaQuery().eq(SystemConfig::getConfigKey, key).last("LIMIT 1"));
        }
    }

    public static <R, T extends BaseMapper<R>> void execute(Class<T> mapperClass, Consumer<T> consumer) {
        try (SqlSession sqlSession = getSqlSessionFactory().openSession(true)) {
            T mapper = sqlSession.getMapper(mapperClass);
            consumer.accept(mapper);
        }
    }

    public static boolean setConfig(String key, Object value) {
        if (value == null) {
            return false;
        }
        String valueStr = StrUtil.toString(value);
        if (StrUtil.isBlank(valueStr)) {
            return false;
        }
        try (SqlSession sqlSession = getSqlSessionFactory().openSession(true)) {
            SystemConfigMapper systemConfigMapper = sqlSession.getMapper(SystemConfigMapper.class);
            SystemConfig systemConfig = getConfig(key);

            if (systemConfig == null) {
                systemConfig = new SystemConfig();
                systemConfig.setConfigKey(key);
                systemConfig.setConfigValue(valueStr);
                return 0 < systemConfigMapper.insert(systemConfig);
            } else {
                return 0 < systemConfigMapper.update(Wrappers.<SystemConfig>lambdaUpdate()
                        .set(SystemConfig::getConfigValue, valueStr)
                        .eq(SystemConfig::getConfigKey, key));
            }

        }
    }
}
